package runner

import (
	"errors"
	"fmt"
	"operator/cicd/util"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

type GoRunner struct {
	config *util.CICDConfig
}

func (g *GoRunner) OS() util.RunOS {
	return g.config.TargetOS
}
func (g *GoRunner) Arch() util.RunArch {
	return g.config.TargetArch
}

func (g *GoRunner) BuildWithOpts(opts ...Options) error {
	tmp := g.config.TempPath
	var err error
	if _, err = os.Stat(tmp); os.IsNotExist(err) {
		os.Mkdir(tmp, 0777)
	} else if err != nil {
		return err
	}

	var cpCmd string
	if runtime.GOOS == "windows" {
		cpCmd = "copy"
	} else {
		cpCmd, err = exec.LookPath("cp")
		if err != nil {
			return err
		}
	}

	goCmd, err := exec.LookPath("go")
	if err != nil {
		return err
	}

	//if _, err := exec.Command(goCmd, "mod", "tidy").CombinedOutput(); err != nil {
	//	return err
	//}

	binName := filepath.Join(g.config.DefaultPath, g.config.ExecName)
	binTemp := filepath.Join(tmp, g.config.ExecName)
	if _, err := os.Stat(binName); err == nil {
		err := os.Remove(binName)
		if err != nil {
			return err
		}
	}

	okCh := make(chan struct{})
	ticker := time.NewTicker(g.config.RunnerTimeOut)

	go func() {
		defer func() {
			okCh <- struct{}{}
		}()
		cmd := exec.Command(goCmd, "build", "-o", binName, g.config.DefaultPath)
		cmd.Env = append(os.Environ(), []string{
			"CGO_ENABLED=0",
			"GOOS=" + g.config.TargetOS.ToString(),
			"GOARCH=" + g.config.TargetArch.ToString(),
		}...)

		var out []byte
		if out, err = cmd.CombinedOutput(); err != nil {
			fmt.Println(fmt.Sprintf("could not build binary: %s\n", string(out)))
			return
		}

		if out, err = NewCopyCommand(cpCmd, binName, binTemp).CombinedOutput(); err != nil {
			fmt.Println(fmt.Sprintf("could not copy binary: %s\n", string(out)))
			return
		}

		for _, i := range g.config.NeedDirOrFile {
			path := i
			d := strings.ReplaceAll(i, "\\", "/")
			arr := strings.Split(d, "/")

			tempath := filepath.Join(tmp, arr[len(arr)-1])
			if _, err = os.Stat(path); err != nil {
				if out, err = NewCopyCommand(cpCmd, path, tempath).CombinedOutput(); err != nil {
					fmt.Println(fmt.Sprintf("could not copy other path: %s\n", string(out)))
					return
				}
			}
		}

		dockerfile := filepath.Join(g.config.DefaultPath, "Dockerfile")

		if _, err = os.Stat(dockerfile); err != nil {
			fmt.Println("not have dockerfile")
			return
		}

		if out, err := NewCopyCommand(cpCmd, dockerfile, filepath.Join(tmp, "Dockerfile")).CombinedOutput(); err != nil {
			fmt.Println(fmt.Sprintf("could not copy dockerfile: %s\n", string(out)))
			return
		}
	}()

	select {
	case <-okCh:
		return nil
	case <-ticker.C:
		return errors.New("time out")
	}
}

func NewCopyCommand(name string, arg ...string) *exec.Cmd {
	if runtime.GOOS == "windows" {
		newarg := append([]string{"/c", name}, arg...)
		return exec.Command("cmd", newarg...)
	}

	return exec.Command(name, arg...)
}

func NewGoRunner(config *util.CICDConfig) Runner {
	return &GoRunner{
		config: config,
	}
}
